package com.spring.nio.service.file.handler;

import com.spring.nio.common.BasicConstant;
import com.spring.nio.core.channel.context.ChannelContext;
import com.spring.nio.core.channel.context.SimpleChannelContext;
import com.spring.nio.core.channel.context.SocketChannelContext;
import com.spring.nio.core.channel.handler.AbstractChannelHandler;
import com.spring.nio.core.event.listener.event.constant.ExceptionEventTypeEnum;
import com.spring.nio.core.event.listener.event.model.ExceptionEventSource;
import com.spring.nio.core.event.listener.event.model.FileHandlerExceptionEventSource;
import com.spring.nio.core.event.model.PacketData;
import com.spring.nio.core.exception.ExceptionManager;
import com.spring.nio.service.file.frame.FileMessageFrame;
import com.spring.nio.service.file.model.structure.FileTransportTaskDTO;
import com.spring.nio.util.LocalTime;
import com.spring.nio.util.NioUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.time.LocalDateTime;
import java.util.*;

/**
 * 文件上传或在线传输实时流处理
 * @author spring
 * */

@Slf4j
@SuppressWarnings("all")
public class FileUploadTransportStreamHandler extends AbstractChannelHandler {

    private FileChannel fileChannel = null;

    @Override
    public void handler(Object o, ChannelContext channelContext) {
        SocketChannelContext socketChannelContext = ((PacketData) o).getSocketChannelContext();
        List<Map<String, Object>> list = (List<Map<String, Object>>) ((SimpleChannelContext) channelContext).getObj();
        if(!CollectionUtils.isEmpty(list)) {

            // 为提升性能，取消FileUploadTask内部的可重入锁导致线程全部排队，使用同步互斥锁进行小范围加锁
            synchronized (FileUploadTransportStreamHandler.class) {
                // 执行排序: 按照帧序号排序,升序排序，此处的顺序必须为1~...,且序号必须连续相差1，否则文件流顺序写入错误，终止写入
                if(!this.sortStreamList(list)) {
                    NioUtil.closedAndRelease(socketChannelContext, Boolean.FALSE, Boolean.FALSE, Boolean.FALSE);
                    log.info("[ " + LocalTime.formatDate(LocalDateTime.now()) + " ] FileUploadTransportStreamHandler | --> 文件在流写入过程出现流索引差值不连续(相差结果不为1)，已终止文件流写入");
                    return;
                }

                // 循环写入
                FileTransportTaskDTO fileTransportTaskDto = null;
                try {
                    for (Map<String, Object> fileTaskMap : list) {
                        if (Objects.isNull(fileTransportTaskDto = (FileTransportTaskDTO) fileTaskMap.get(BasicConstant.FILE_TASK))) {
                            continue;
                        }

                        // 该方法一旦异常，执行异常处理，FileUploadTransportStreamHandler
                        this.writeBytes(fileTransportTaskDto, fileTaskMap, socketChannelContext);
                    }
                } catch (Exception e) {
                    // 异常事件(ExceptionEvent) 事件源类型为异常事件源类型(EXCEPTION_EVENT_SOURCE)  事件源数据(NioReadEventExceptionSource)
                    FileHandlerExceptionEventSource fileHandlerExceptionEventSource = new FileHandlerExceptionEventSource();
                    fileHandlerExceptionEventSource.setSocketChannelContext(socketChannelContext);
                    fileHandlerExceptionEventSource.setFileTransportTaskDto(fileTransportTaskDto);
                    fileHandlerExceptionEventSource.setModule("FileUploadTransportStreamHandler --> method [handler] --> 文件上传流帧处理异常");
                    fileHandlerExceptionEventSource.setException(e);
                    fileHandlerExceptionEventSource.setExceptionEventTypeEnum(ExceptionEventTypeEnum.FILE_TRANSPORT_HANDLER_IO_EXCEPTION);
                    fileHandlerExceptionEventSource.setIsSend(Boolean.TRUE);
                    ExceptionManager.handleException(fileHandlerExceptionEventSource);
                } finally {
                    // 发送完成后清空文件流list数据,如果不清空下次发送回重复进行发送
                    list.clear();
                }
            }
        }
    }

    /**
     * 写入发送过来的文件流数据
     * @param fileTransportTaskDto
     * @param fileTaskMap
     * @param socketChannelContext
     * @throws IOException
     */
    private void writeBytes(FileTransportTaskDTO fileTransportTaskDto, Map<String, Object> fileTaskMap, SocketChannelContext socketChannelContext) throws IOException {
        // 设置任务为处理中
        fileTransportTaskDto.setTaskStatus("HANDLING");
        // 文件帧
        FileMessageFrame fileMessageFrame = (FileMessageFrame) fileTaskMap.get(BasicConstant.FILE_FRAME);

        // 原始文件字节流数据
        byte[] originFileStreamData = (byte[]) fileTaskMap.get(BasicConstant.ORIGIN_FILE_STREAM_DATA);
        this.fileChannel = fileTransportTaskDto.getFileChannel();
        if(!this.fileChannel.isOpen()) {
            // 文件通道未打开，写入失败，释放资源
            NioUtil.closedAndRelease(socketChannelContext, Boolean.FALSE, Boolean.FALSE, Boolean.FALSE);
            log.info("[ " + LocalTime.formatDate(LocalDateTime.now()) + " ] FileUploadTransportStreamHandler | --> 文件 [{}] 通道未打开，已终止文件流写入", fileTransportTaskDto.getFile().getName());
        }

        // 向文件通道写入数据
        fileTransportTaskDto.setPacketIndex(fileMessageFrame.getFrameIndex());
        fileTransportTaskDto.setSumSendBytes(fileTransportTaskDto.getSumSendBytes() + this.fileChannel.write(ByteBuffer.wrap(originFileStreamData, 0, originFileStreamData.length)));
        if(fileTransportTaskDto.getFileLength().compareTo(fileTransportTaskDto.getSumSendBytes()) == 0) {
        }
        //log.info("[ " + LocalTime.formatDate(LocalDateTime.now()) + " ] FileUploadTransportStreamHandler | --> 服务端已上传 [ 文件名: {} <---> 字节数: {} ]", fileTransportTaskDto.getFile().getName(), fileTransportTaskDto.getSumSendBytes());
    }

    /**
     * 按照帧序号排序,升序排序，此处的顺序必须为1~...
     * @param list
     */
    private Boolean sortStreamList(List<Map<String, Object>> list) {
        if(list.size() == 1) {
            return Boolean.TRUE;
        }

        // 先自增排序
        //list.stream().sorted(Comparator.comparing(EmployeePayrollProfileDTO::getEffectiveStartDate).reversed())
        Collections.sort(list, new Comparator<Map<String, Object>>() {
            @Override
            public int compare(Map<String, Object> m1, Map<String, Object> m2) {
                Integer index1 = ((FileMessageFrame) m1.get(BasicConstant.FILE_FRAME)).getFrameIndex();
                Integer index2 = ((FileMessageFrame) m2.get(BasicConstant.FILE_FRAME)).getFrameIndex();
                return index1.compareTo(index2);
            }
        });

        // 在判断元素间的差值是否为1
        for(int i = 0; i < (list.size() - 1); i++) {
            Integer index1 = ((FileMessageFrame) list.get(i).get(BasicConstant.FILE_FRAME)).getFrameIndex();
            Integer index2 = ((FileMessageFrame) list.get(i + 1).get(BasicConstant.FILE_FRAME)).getFrameIndex();
            if(!((index2 - index1) == 1)) {
                System.out.println("差值不为1，发送失败, 当前list中的帧序号为: " + index1 + "~" + index2);
                return Boolean.FALSE;
            }
        }

        return Boolean.TRUE;
    }
}
